class CacheClass {
    private dbName: string;
    private storeName: string;
    private db: IDBDatabase | null;

    constructor(dbName: string = 'XC_DB', storeName: string = 'LIB_STORE_XCACHE') {
        this.dbName = dbName;
        this.storeName = storeName;
        this.db = null;
    }

    open() {
        return new Promise<CacheClass>((resolve, reject) => {
            const request = window.indexedDB.open(this.dbName, 1);

            request.onerror = () => {
                reject('Error opening database');
            };

            request.onsuccess = () => {
                this.db = request.result;
                resolve(this);
            };

            request.onupgradeneeded = (event) => {
                // @ts-ignore
                const db = event.target.result;
                db.createObjectStore(this.storeName, {keyPath: 'key'});
            };
        });
    }

    closeDB() {
        this.db?.close();
        this.db = null;
    }

    async clear() {
        if (this.db == null) {
            await this.open();
        }
        const transaction = this.db?.transaction(this.storeName, 'readwrite');
        const store = transaction?.objectStore(this.storeName);
        const request = store?.clear();
        if (!request) {
            throw new Error('Error clearing cache');
        }
        request.onerror = () => {
            throw new Error('Error clearing cache');
        };

        request.onsuccess = () => {
            // console.log('Cache cleared');
        };
    }

    get(key: string | number | object | Date) {
        let k = this.key(key);
        return new Promise(async (resolve, reject) => {
            if (this.db == null) {
                await this.open();
            }
            const transaction = this.db?.transaction(this.storeName, 'readonly');
            const store = transaction?.objectStore(this.storeName);
            const request = store?.get(k);
            if (!request) {
                reject(`Error getting value for key: ${k}`);
                return;
            }
            request.onerror = (e) => {
                reject(`Error getting value for key: ${k}`);
            };

            request.onsuccess = () => {
                const result = request.result;
                if (result) {
                    if (result.expiry && result.expiry < Date.now()) {
                        resolve(null);
                        // Delete the key if the item is expired
                        this.remove(key);
                        return;
                    }
                    resolve(result.value);
                } else {
                    resolve(null);
                }
            };
        });
    }

    private key(key: object | number | string | Date): string | number | Date {
        if (typeof key === 'object') {
            return JSON.stringify(key);
        } else {
            return key;
        }
    }

    set(key: object | number | string | Date, value: any, expiryTime: number | null = null) {
        let k = this.key(key);
        return new Promise<void>(async (resolve, reject) => {
            if (this.db == null) {
                await this.open();
            }
            const transaction = this.db?.transaction(this.storeName, 'readwrite');
            const store = transaction?.objectStore(this.storeName);
            const data = {
                key: k,
                value,
                expiry: expiryTime ? Date.now() + (expiryTime * 1000) : null
            };
            const request = store?.put(data);
            if (!request) {
                reject(`Error setting value for key: ${k}`);
                return;
            }
            request.onerror = () => {
                reject(`Error setting value for key: ${k}`);
            };

            request.onsuccess = () => {
                resolve();
            };
        });
    }

    remove(key: Object | string | number | Date) {
        let k = this.key(key);
        return new Promise<void>(async (resolve, reject) => {
            if (this.db == null) {
                await this.open();
            }

            const transaction = this.db?.transaction(this.storeName, 'readwrite');
            const store = transaction?.objectStore(this.storeName);
            const request = store?.delete(k);
            if (!request) {
                reject(`Error deleting value for key: ${k}`);
                return;
            }
            request.onerror = () => {
                reject(`Error deleting value for key: ${k}`);
            };

            request.onsuccess = () => {
                resolve();
            };
        });
    }
}

export const cache = new CacheClass();

export const buildCache = (dbName: string, storeName: string) => {
    return new CacheClass(dbName, storeName);
}
